# -*- coding: utf-8 -*-

from hlm.node import *
from hlm.channel import ChannelProperty as cp

from ip.fabrics import ChannelExecOpFabric
from ip.opdef import AluOpCode


class ChannelERunnerSoftmem(Channel):
    # Sender (output)
    lanes_map = {
        'Read': {
            cp.PORTS: [('dst_uri', -1, int), ('sb', -1, bool),
                       ('r_addr_0', -1, int), ('r_addr_0_wait', -1, bool), ('r_ain_0_wait', -1, bool),
                       ('r_addr_1', -1, int), ('r_addr_1_wait', -1, bool), ('r_ain_1_wait', -1, bool),
                       ('op_code', -1, AluOpCode), ('dst_pid', -1, int)],
            cp.GET: ['dst_pid', 'sb', 'dst_uri',
                     'r_addr_0', 'r_addr_0_wait', 'r_ain_0_wait',
                     'r_addr_1', 'r_addr_1_wait', 'r_ain_1_wait', 'op_code']
        }
    }


class NodeERunner(Node):
    """ Only 2 tasks needed to be done.
    1. Fetch an E-type Nano OP if current OP is already completed execution.
    2. Dispatch the instructions when holding the OP.

    Please notice that ERunner ONLY accept an E-type Nano OP.
    """
    def initialize(self, params: ObjectDict) -> None:
        params.num_of_alu: int
        params.peer_id: int
        params.total_peers: int
        params.base_ptrs: 'List.int'
        # Save the parameters.
        self.const.num_of_alu = params.num_of_alu
        # Svae the peer id.
        self.const.peer_id = params.peer_id
        self.const.total_peers = params.total_peers
        # Save the base pointers.
        # Base pointers delineate the private address spaces in which
        # individual nodes can access. For SIMD execution modes where multiple
        # nodes are virtually-linked, the base pointer is an address offset to
        # the runner input, which generates different read requests to the
        # respective spaces a node can access.
        self.const.base_ptrs = params.base_ptrs
        # Set the running state.
        self.reg.is_running = False
        # Set the Nano OP registers.
        self.reg.completed_alu = 0
        self.reg.nano_op_count = 0
        # Nano OP dispatch pointer.
        self.reg.alu_arbiter = 0
        # Prepare the alu data.
        self.reg.alu_enable = [False for _ in range(self.const.num_of_alu)]
        self.reg.alu_dst = [0 for _ in range(self.const.num_of_alu)]
        self.reg.alu_src_addr0 = [0 for _ in range(self.const.num_of_alu)]
        self.reg.alu_src_addr1 = [0 for _ in range(self.const.num_of_alu)]
        self.reg.alu_len_count = [0 for _ in range(self.const.num_of_alu)]

        # Prepare the port for the Nano OP receiver.
        self.port.nano_op_receiver = self.port_channel_receiver(ChannelExecOpFabric)
        # Prepare the connections for ALUs.
        self.port.softmem_senders = [self.port_channel_sender(ChannelERunnerSoftmem)
                                     for _ in range(self.const.num_of_alu)]

    tasks = ['task_dispatch_execution',
             'task_fetch_op']

    def task_fetch_op(self):
        # Check running state.
        if self.reg.is_running:
            return
        # Check if any ALU channel can accept ERunner sequence.
        if not self.port.nano_op_receiver.Bus.is_any_empty():
            # Fetch E-type nano instruction.
            self.reg.nano_op = self.port.nano_op_receiver.Bus.get()
            # Reset the Nano OP Count.
            self.reg.completed_alu = 0
            self.reg.nano_op_count = 0
            # Init the complete ALU count.
            # Check for the ALU setting.
            if self.reg.nano_op.alu == 0:
                # Use the arbiter pointer.
                self.reg.nano_op.alu = 1 << self.reg.alu_arbiter
                # Change the alu arbiter.
                self.reg.alu_arbiter += 1
                if self.reg.alu_arbiter >= self.const.num_of_alu:
                    self.reg.alu_arbiter = 0
            # Calculate the dst address offset.
            self.reg.peer_count = 0
            for bin_id in range(self.const.peer_id):
                if (self.reg.nano_op.peer_id & (1 << bin_id)) > 0:
                    self.reg.peer_count += 1
            self.reg.alu_count = 0
            for bin_id in range(self.const.num_of_alu):
                if (self.reg.nano_op.alu & (1 << bin_id)) > 0:
                    self.reg.alu_count += 1
            # Based on the Nano OP, check the ALU settings.
            for ii in range(self.const.num_of_alu):
                self.reg.alu_mask = 1 << ii
                # Check whether the instruction should be deployed.
                if (int(self.reg.nano_op.alu) & self.reg.alu_mask) > 0:
                    self.reg.alu_offset = 0
                    for bin_id in range(self.const.num_of_alu):
                        if self.reg.nano_op.alu & (1 << bin_id) and bin_id < ii:
                            self.reg.alu_offset += 1
                    self.reg.total_alu_offset = self.reg.peer_count * self.reg.alu_count + self.reg.alu_offset
                    # Calculate the destination offset.
                    self.reg.alu_dst[ii] = self.reg.nano_op.dst_addr + self.reg.total_alu_offset * self.reg.nano_op.dst_increase
                    self.reg.alu_src_addr0[ii] = self.reg.nano_op.src_addr_0
                    self.reg.alu_src_addr1[ii] = self.reg.nano_op.src_addr_1
                    self.reg.alu_len_count[ii] = 0
                    # Enable the ALU.
                    self.reg.alu_enable[ii] = True
                    # Increase the nano op count.
                    self.reg.nano_op_count += 1
                else:
                    # Disable the ALU.
                    self.reg.alu_enable[ii] = False
            # Check the nano op count.
            if self.reg.nano_op_count > 0:
                # Set the running state, wait for execution.
                self.reg.is_running = True

    def task_dispatch_execution(self):
        # Check running state.
        if not self.reg.is_running:
            return
        # Check if any ALU channel can accept ERunner sequence.
        for ii in range(self.const.num_of_alu):
            # Check whether the port is available and ready to receive.
            if not self.reg.alu_enable[ii] or \
                    self.port.softmem_senders[ii].Read.is_any_full():
                continue
            # Send the data to bus.
            self.port.softmem_senders[ii].Read.put(
                dst_pid=self.reg.nano_op.dst_peer_id,
                sb=self.reg.nano_op.dst_sb,
                dst_uri=self.reg.alu_dst[ii] + self.const.base_ptrs[ii]
                if self.reg.nano_op.dst_enable_offset else self.reg.alu_dst[ii],
                r_addr_0=self.reg.alu_src_addr0[ii] + self.const.base_ptrs[ii]
                if self.reg.nano_op.src_enable_offset_0 else self.reg.alu_src_addr0[ii],
                r_addr_0_wait=self.reg.nano_op.src_wait_passin_0,
                r_ain_0_wait=self.reg.nano_op.src_wait_aluin_0,
                r_addr_1=self.reg.alu_src_addr1[ii] + self.const.base_ptrs[ii]
                if self.reg.nano_op.src_enable_offset_1 else self.reg.alu_src_addr1[ii],
                r_addr_1_wait=self.reg.nano_op.src_wait_passin_1,
                r_ain_1_wait=self.reg.nano_op.src_wait_aluin_1,
                op_code=self.reg.nano_op.op_code)
            # Increase the length counter.
            self.reg.alu_len_count[ii] += 1
            # Check whether we have already reach the len_count limitation.
            if self.reg.alu_len_count[ii] < self.reg.nano_op.len_limit:
                # Update the ALU destination.
                self.reg.alu_dst[ii] += self.reg.nano_op.dst_stride
                # Update the src address.
                self.reg.alu_src_addr0[ii] += self.reg.nano_op.src_stride_0
                if self.reg.alu_src_addr0[ii] >= self.reg.nano_op.src_limit_0:
                    self.reg.alu_src_addr0[ii] = self.reg.nano_op.src_addr_0
                self.reg.alu_src_addr1[ii] += self.reg.nano_op.src_stride_1
                if self.reg.alu_src_addr1[ii] >= self.reg.nano_op.src_limit_1:
                    self.reg.alu_src_addr1[ii] = self.reg.nano_op.src_addr_1
            else:
                # Reach the limit, finish.
                self.reg.completed_alu += 1
                # Clear the running flag.
                self.reg.alu_enable[ii] = False
        # Check all ALUs have been completed.
        if self.reg.completed_alu == self.reg.nano_op_count:
            # Finish running.
            self.reg.is_running = False
